feat(grants): add DELETE /v1/grants/{grantId} for owner-driven revocation - #80
Open
tnunamak wants to merge 1 commit into
Open
feat(grants): add DELETE /v1/grants/{grantId} for owner-driven revocation#80tnunamak wants to merge 1 commit into
tnunamak wants to merge 1 commit into
Conversation
tnunamak
enabled auto-merge (squash)
May 1, 2026 21:28
Codex ReviewFindings
Verification I attempted to run |
tnunamak
force-pushed
the
tim/account-app-owner-auth
branch
from
August 1, 2026 20:59
1ffc9c5 to
48723eb
Compare
volod-vana
added a commit
that referenced
this pull request
Aug 27, 2026
…iation (#227) Personal server slice of durable data point deletion. Sibling PRs data-gateway #80 (tombstone DELETE) and vana-sdk #195 (deleteDataPoint) are merged. Rebased onto the write API (#226) and derivative lineage (#228). Today a local delete is not durable: the copy goes, sync pulls it back from the gateway on the next cycle. This PR makes delete stick and closes that resurrection bug. `DELETE /v1/data/:scope` (owner auth, same gate as ingest) runs, in this order: 1. look up the current version on the gateway 2. sign a tombstone AddData for current+1 with the server signer, exactly as uploads are signed 3. `DELETE /v1/data/:dataPointId` on the gateway; on 409 re-sign once at the version the gateway names 4. delete blobs from vana-storage by exact key, one `DELETE /{owner}/{scope}/{version}` per version at or below the tombstone, never by prefix 5. local `deleteScope` 6. access log entry with `action: "delete"` The response is a 200 with the per-step result (this replaces the old 204; nothing in unity-surfaces or desktop calls this route). If the gateway does not acknowledge, the local copy is kept and the route returns 502. Blob deletion after a gateway success is best effort: failures leave exact `{scope, version}` markers (range markers for the registry sequence) that every sync cycle drains at 15 keys per pass, half of the storage DELETE budget. Nothing ever falls back to a prefix delete, so a concurrent re-add, which lives above the tombstone version, survives by construction. Reads: a `ScopeDeletionTracker` shared by the HTTP API, the MCP read paths, the paid session port and the sync workers refuses a scope the gateway reports deleted, before any x402 charge. It is fed by the includeDeleted feed and by this replica's own deletes, consulted synchronously, and only falls through to a gateway lookup on a local miss or a verdict older than 120s. Consistency window is stated in the code: immediate for this replica's deletes, one poll interval for another replica's while sync is healthy. PS-Lite wires the same tracker. Sync reconciliation is causal, not clock based: synced rows are covered by registry version; unsynced rows are covered unless they carry an `afterTombstoneVersion` marker stamped at ingest (sqlite schema v4, lite and memory indexes). Covered entries are dropped and their exact blob keys queued for cleanup. Delete and sync cycles are serialised through the sync manager lock; every persisted marker store mutates through a queue. The reproduction test is `download.test.ts > deletion reconciliation (durable delete) > does not resurrect a scope the gateway reports as deleted`. It fails against main's download worker and passes here. Scope keys, stated plainly: the scope key is derived (HKDF from the owner master-key signature) and never stored, on both PS and PS-Lite. There is nothing to destroy on delete. Durability comes from the tombstone plus blob removal; ciphertext that left before the delete stays decryptable by the owner. If that is not acceptable for the health use case, per-scope stored keys are a separate change. Tombstone constants are a local copy pinned to the same hex as the SDK export, with a TODO to switch once a canary ships. Lineage: `DELETE /v1/data/:scope?cascade=lineage` stays 501. Durable delete exists now, what is missing is the walk over the gateway graph (owner view, deepest first, all-or-nothing before the first tombstone, partial reporting). Single-node delete of a source leaves derivatives in place; the lineage view reports the source as deleted per node. The lineage read endpoint is not gated by the deletion tracker on purpose: it is a signed gateway read and the view already carries `deletedAt`. Known limits: - a scope with thousands of versions drains at 15 keys per cycle; faster needs a bulk endpoint on the storage side - data ingested on a replica after another replica's delete, before the first replica learns of it, is treated as covered - follow-up: advance the incremental `since` watermark on the gateway's new `changedAt` so revives are picked up without a full re-list Tests: 1272 unit (1148 on main) plus 26 e2e. Build must precede test, as in CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the missing companion to
POST /v1/grants. Owner can now revoke a grant by id without producing a wallet popup; PS signsGrantRevocationEIP-712 with its delegatedserverSignerand submits to the gateway.Why
Account-app-driven flows (e.g. account.vana.org's account-action revocation UI in PR vana-com/vana-connect#112) need a programmatic revoke path that does not invoke a user wallet popup at revoke time. PS already exposes
POST /v1/grantsfor the create side; this is the symmetric delete side.Behavior
serverOwner, Bearer access token, or dev token — same model asPOST /).GrantRevocation { grantorAddress, grantId }viaserverSigner.signGrantRevocation.gateway.revokeGrant({ grantId, grantorAddress, signature }).{ status: 'revoked', grantId }on success.Tests
serverSignerreturns 500packages/server/src/routes/grants.test.ts— 18 pass / 0 fail.Scope
Single route added. No middleware changes. No changes to existing routes. No client-side changes (gateway client already exposes
revokeGrant).